package com.bianguo.android.ioclibrary;

import android.os.Handler;
import android.os.Looper;

import com.bianguo.android.ioclibrary.annotations.Subscribe;
import com.bianguo.android.ioclibrary.bean.EventBean;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EventBus {

    private static volatile EventBus instance;
    private HashMap<Object, ArrayList<EventBean>> eventMap;
    private Handler mHandler;
    private ExecutorService executorService;

    private EventBus() {
        eventMap = new HashMap<>();
        mHandler = new Handler();
        executorService = Executors.newCachedThreadPool();
    }

    public static EventBus getDefault() {
        EventBus eventBus = instance;
        if (eventBus == null) {
            synchronized (EventBus.class) {
                if (eventBus == null) {
                    eventBus = new EventBus();
                    instance = eventBus;
                }
            }
        }
        return eventBus;
    }

    public void register(Object target) {
        if (isRegister(target)) {
            return;
        }
        ArrayList<EventBean> events = findEvents(target);
        if (events != null && events.size() > 0) {
            eventMap.put(target, events);
        }
    }

    public boolean isRegister(Object target) {
        return eventMap.containsKey(target);
    }

    public void unRegister(Object target) {
        eventMap.remove(target);
    }

    public void post(Object event) {
        Iterator<Object> iterator = eventMap.keySet().iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            ArrayList<EventBean> eventBeans = eventMap.get(obj);
            for (EventBean eventBean : eventBeans) {
                if (eventBean.getParamType().isAssignableFrom(event.getClass())) {
                    switch (eventBean.getThreadMode()) {
                        case IO:
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                //主 --> 子
                                executorService.execute(() -> {
                                    invoke(eventBean, obj, event);
                                });
                            } else {
                                //子 --> 子
                                invoke(eventBean, obj, event);
                            }
                            break;
                        case MAIN:
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                //主 --> 主
                                invoke(eventBean, obj, event);
                            } else {
                                //子 --> 主
                                mHandler.post(() -> {
                                    invoke(eventBean, obj, event);
                                });
                            }
                            break;
                    }
                }
            }
        }
    }

    private void invoke(EventBean eventBean, Object obj, Object type) {
        Method method = eventBean.getMethod();
        try {
            method.setAccessible(true);
            method.invoke(obj, type);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private ArrayList<EventBean> findEvents(Object target) {
        ArrayList<EventBean> eventBeans = new ArrayList<>();
        Class<?> clazz = target.getClass();
        while (clazz != null) {
            String clazzName = clazz.getName();
            if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||
                    clazzName.startsWith("android.")) {
                break;
            }
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                Subscribe subscribe = method.getAnnotation(Subscribe.class);
                if (subscribe != null) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    if (parameterTypes.length == 1) {
                        EventBean eventBean = new EventBean();
                        eventBean.setMethod(method);
                        eventBean.setParamType(parameterTypes[0]);
                        eventBean.setThreadMode(subscribe.thread());
                        eventBeans.add(eventBean);
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
        return eventBeans;
    }
}
